﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.Threading;
using Niknak.Services.ServiceCore;
using Niknak.Services.TaskScheduler.Interfaces;

namespace Niknak.Services.TaskScheduler
{
    public class Engine<ExceptionHandlerType, ScheduledTaskType, AuditLogType, AuditLogEntryType> : IServiceEngine
        where ExceptionHandlerType : IExceptionHandler, new()
        where ScheduledTaskType : Interfaces.DataModel.IScheduledTask, new()
        where AuditLogType : Interfaces.DataModel.IAuditLog<AuditLogEntryType>, new()
        where AuditLogEntryType : Interfaces.DataModel.IAuditLogEntry
    {
        private ServiceHost _wcfEndpointHost = null;
        private List<IControlledTask> _scheduledTasks = new List<IControlledTask>();

        public static ExceptionHandlerType CustomExceptionHandler
        {
            get { return new ExceptionHandlerType(); }
        }

        /// <summary>
        /// Raised when the engine is starting up before any code is loaded
        /// </summary>
        public event EventHandler EngineStarting;

        public void Start()
        {
            CustomExceptionHandler.WriteToEventLog(
                "Starting Task Scheduler Service",
                System.Diagnostics.EventLogEntryType.Information,
                Constants.EventLogIds.SERVICESTART
            );

            if (EngineStarting != null)
                EngineStarting(this, EventArgs.Empty);

            HostWcfEndpoint();
            Initialise();
        }

        private void HostWcfEndpoint()
        {
            try
            {
                //start the wcf endpoint
                WcfEndpoint<ExceptionHandlerType, ScheduledTaskType, AuditLogType, AuditLogEntryType> singletonEndpoint = new WcfEndpoint<ExceptionHandlerType, ScheduledTaskType, AuditLogType, AuditLogEntryType>();
                singletonEndpoint.DoReset = Reset;
                singletonEndpoint.DoRestart = Restart;
                singletonEndpoint.DoRaiseIterationCompletedEvent = RaiseIterationCompletedEvent;

                _wcfEndpointHost = new System.ServiceModel.ServiceHost(singletonEndpoint);
                _wcfEndpointHost.Open();

                Console.WriteLine("WCF endpoint running");
                CustomExceptionHandler.WriteToEventLog(
                    "WCF endpoint running",
                    System.Diagnostics.EventLogEntryType.Information,
                    Constants.EventLogIds.WCFENDPOINTRUNNING
                );
            }
            catch (Exception ex)
            {
                CustomExceptionHandler.WriteToEventLog(
                    "Unable to host WCF endpoint: " + ex.Message,
                    System.Diagnostics.EventLogEntryType.Error,
                    Constants.EventLogIds.WCFENDPOINTSTARTFAIL
                );
            }
        }

        private void Initialise()
        {
            Console.WriteLine("Initialising...");
            CustomExceptionHandler.WriteToEventLog(
                "Initialising...",
                System.Diagnostics.EventLogEntryType.Information,
                Constants.EventLogIds.INITIALISING
            );

            _scheduledTasks.Clear();
            ScheduledTaskType taskType = new ScheduledTaskType();

            _scheduledTasks = taskType.GetAllScheduledTasks()
                .Where(st => st.IsActive && (st.TaskType != Enums.TaskType.Advanced || (st.TaskType == Enums.TaskType.Advanced && new AdvancedTask<ExceptionHandlerType, ScheduledTaskType, AuditLogType, AuditLogEntryType>((ScheduledTaskType)st).CalculateTargetEnd() > DateTime.UtcNow)))
                .Select(st => 
                {
                    IControlledTask newTask = null;
                    switch (st.TaskType)
                    {
                        case Enums.TaskType.Simple:
                            newTask = new SimpleTask<ExceptionHandlerType, ScheduledTaskType, AuditLogType, AuditLogEntryType>((ScheduledTaskType)st);
                            break;
                        case Enums.TaskType.Advanced:
                            newTask = new AdvancedTask<ExceptionHandlerType, ScheduledTaskType, AuditLogType, AuditLogEntryType>((ScheduledTaskType)st);
                            break;
                    }
                    return newTask;
                })
                .ToList();

            _scheduledTasks.ForEach(st => 
            {
                try
                {
                    st.Initialise();
                    Console.WriteLine("Started schedule for task " + st.CustomSettings.Name);
                    CustomExceptionHandler.WriteToEventLog(
                        "Started schedule for task " + st.CustomSettings.Name,
                        System.Diagnostics.EventLogEntryType.Information,
                        Constants.EventLogIds.STARTEDTASK
                    );
                }
                catch (Exception ex)
                {
                    CustomExceptionHandler.ExceptionLogEntry(
                        new Exception(
                            "Unable to start schedule for task " + st.CustomSettings.Name + ": " + ex.Message,
                            ex
                        )
                    );
                    CustomExceptionHandler.WriteToEventLog(
                        "Unable to start schedule for task " + st.CustomSettings.Name + ": " + ex.Message,
                        System.Diagnostics.EventLogEntryType.Error,
                        Constants.EventLogIds.TASKSTARTFAIL
                    );
                }
            });

            Console.WriteLine("Initialisation complete");
            CustomExceptionHandler.WriteToEventLog(
                "Initialisation complete",
                System.Diagnostics.EventLogEntryType.Information,
                Constants.EventLogIds.INITIALISATIONCOMPLETE
            );
        }

        public bool Reset()
        {
            Console.WriteLine("Doing a manual reset");
            CustomExceptionHandler.WriteToEventLog(
                "Doing a manual reset",
                System.Diagnostics.EventLogEntryType.Information,
                Constants.EventLogIds.RESETTING
            );

            try
            {
                ShutdownTasks(true);
                Initialise();
                return true;
            }
            catch { return false; }
        }

        public bool Restart()
        {
            Console.WriteLine("Doing a manual restart");
            CustomExceptionHandler.WriteToEventLog(
                "Doing a manual restart",
                System.Diagnostics.EventLogEntryType.Information,
                Constants.EventLogIds.RESTARTING
            );

            try
            {
                ShutdownTasks(false);
                Environment.Exit(0);
                return true;
            }
            catch { return false; }
        }

        /// <summary>
        /// Allows for the IterationCompletedEvent attached to a specific task to be raised against a given iteration date context.
        /// </summary>
        /// <param name="scheduledTaskId">Guid to identify the task</param>
        /// <param name="iterationDate">Iteration date for context</param>
        /// <returns>True when the event has been spun off into a separate thread</returns>
        /// <remarks>Typically called via the WCF endpoint. Means tasks with 'EnableCompletedEvent' set to false can still have their event raised after the task has run.</remarks>
        public bool RaiseIterationCompletedEvent(Guid scheduledTaskId, DateTime iterationDate)
        {
            try
            {
                IControlledTask task = _scheduledTasks.Single(st => st.CustomSettings.ScheduledTaskId == scheduledTaskId);

                if (task == null)
                    return false;

                Utils.QueueUserWorkItem(new { Task = task, IterationDate = iterationDate }, (data) => data.Task.RaiseIterationCompletedEvent(data.IterationDate));
                return true;
            }
            catch { return false; }
        }

        private void ShutdownTasks(bool withContinue)
        {
            Console.WriteLine("Shutting down tasks...");
            CustomExceptionHandler.WriteToEventLog(
                "Shutting down tasks...",
                System.Diagnostics.EventLogEntryType.Information,
                Constants.EventLogIds.TASKSSHUTTINGDOWN
            );

            _scheduledTasks.ForEach(st =>
            {
                st.Shutdown();
                while (st.IsRunning)
                    Thread.Sleep(100);

                if (withContinue)
                    st.ResetShuttingdown();
            });
        }

        public void Stop()
        {
            CustomExceptionHandler.WriteToEventLog(
                "Stopping Task Scheduler Service",
                System.Diagnostics.EventLogEntryType.Information,
                Constants.EventLogIds.SERVICESTOP
            );

            ShutdownTasks(false);
            Thread.Sleep(1000);
            if (_wcfEndpointHost != null)
                _wcfEndpointHost.Close();
            Thread.Sleep(1000);
        }
    }
}
